package qbfiles;

import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;

import misc.ImprovedByteStream;
import res.KeyWords;

public class QbParser {
  private ImprovedByteStream bs;
  private Map<Integer, String> fields;
  private StringBuffer output;
  private long pseudo = 0;
  private String indent;
  private boolean elseIfPossible = false;
  private boolean breakLineAfterThis = false;
  
  public QbParser(ImprovedByteStream bs){
    this.bs = bs;
    fields = new HashMap<Integer, String>();
  }
  
  public void setFields(Map<Integer, String> fields){
    this.fields = fields;
  }
  
  public String parse(){
    pseudo = 1;
    indent = "";
    output = new StringBuffer();
    while(makeDecision());
    return output.toString();
  }
  
  public int getTablePosition(){
    output = new StringBuffer();
    while(makeDecision());
    return bs.getPosition();
  }
  
  private boolean makeDecision(){
    boolean requestNewLineAfterNext = false;
    //System.out.println(bs.getPosition());
    
    byte[] cmd = new byte[1];
    bs.read(cmd);
            
    switch(cmd[0]){
      case 0x00:
        break;
        
      case 0x01:
        output.append("\n" + lineBegin());
        break;
        
      case 0x02:
        output.append("\n" + lineBegin(parseLong()));
        break;
      
      case 0x03:
        increaseNesting();
        output.append(KeyWords.STRUCT + KeyWords.CURLY_BRACET_OPEN + "\n" + lineBegin());
        breakLineAfterThis = false;
        break;
      
      case 0x04:
        decreaseNesting();
        output.append(KeyWords.CURLY_BRACET_CLOSE + "\n" + lineBegin());
        break;
      
      case 0x05:
        increaseNesting();
        output.append(KeyWords.ARRAY + KeyWords.ROUND_BRACET_OPEN + "\n" + lineBegin());
        break;
      
      case 0x06:
        decreaseNesting();
        output.append("\n" + lineBegin() + KeyWords.ROUND_BRACET_CLOSE + "\n" + lineBegin());
        break;
      
      case 0x07:
        output.append(KeyWords.EQUALS + " ");
        requestNewLineAfterNext = true;
        break;
      
      case 0x08:
        output.append("0x08 ");
        break;
      
      case 0x09:
        output.append("0x09 ");
        break;
      
      case 0x0A:
        output.append(KeyWords.MINUS + " ");
        break;
      
      case 0x0B:
        output.append(KeyWords.PLUS + " ");
        break;
      
      case 0x0C:
        output.append(KeyWords.DIVIDE + " ");
        break;
      
      case 0x0D:
        output.append(KeyWords.MULTI + " ");
        break;
      
      case 0x0E:
        output.append(KeyWords.ROUND_BRACET_OPEN + " ");
        break;
      
      case 0x0F:
        output.append(KeyWords.ROUND_BRACET_CLOSE + " ");
        break;
      
      case 0x10:
        output.append("0x10 ");
        break;
      
      case 0x11:
        output.append("0x11 ");
        break;
      
      case 0x12:
        output.append(KeyWords.LESS_THAN + " ");
        break;
      
      case 0x13:
        output.append(KeyWords.LESS_OR_EQUAL + " ");
        break;
      
      case 0x14:
        output.append(KeyWords.GREATER_THAN + " ");
        break;
      
      case 0x15:
        output.append(KeyWords.GREATER_OR_EQUAL + " ");
        break;
      
      case 0x16:
        output.append(parseField() + " ");
        break;
      
      case 0x17:
        output.append(parseLong() + " ");
        break;
        
      case 0x18:
        output.append("0x18 ");
        break;
      
      case 0x19:
        output.append("0x19 ");
        break;
      
      case 0x1A:
        output.append(parseFloat() + " ");
        break;
      
      case 0x1B:
        output.append(KeyWords.STRING_A + parseString() + KeyWords.STRING_A + " ");
        break;
      
      case 0x1C:
        output.append(KeyWords.STRING_B + parseString() + KeyWords.STRING_B + " ");
        break;
      
      case 0x1D:
        output.append("0x1D ");
        break;
      
      case 0x1E:
        output.append(KeyWords.VECTOR + KeyWords.SQUARED_BRACET_OPEN + 
            parseFloat() + KeyWords.DIVIDER + " " + 
            parseFloat() + KeyWords.DIVIDER + " " + 
            parseFloat() + KeyWords.SQUARED_BRACET_CLOSE + " ");
        break;
      
      case 0x1F:
        output.append(KeyWords.PAIR + KeyWords.SQUARED_BRACET_OPEN + 
            parseFloat() + KeyWords.DIVIDER + " " + 
            parseFloat() + KeyWords.SQUARED_BRACET_CLOSE + " ");
        break;
      
      case 0x20:
        output.append(KeyWords.LOOP + " ");
        increaseNesting();
        break;
      
      case 0x21:
        output.delete(output.length() - 2, output.length());
        output.append(KeyWords.END_LOOP + " ");
        decreaseNesting();
        break;
      
      case 0x22:
        output.append(KeyWords.BREAK + "\n" + lineBegin());
        break;
      
      case 0x23:
        output.append(KeyWords.FUNCTION + " ");
        increaseNesting();
        break;
      
      case 0x24:
        output.delete(output.length() - 2, output.length());
        output.append(KeyWords.END_FUNCTION + "\n" + lineBegin());
        decreaseNesting();
        break;
      
      case 0x25:
        output.append(KeyWords.IF + " ");
        increaseNesting();
        break;
      
      case 0x26:
        output.delete(output.length() - 2, output.length());
        output.append(KeyWords.ELSE + " ");
        elseIfPossible = true;
        return true;
      
      case 0x27:
        output.append("0x23 ");
        break;
      
      case 0x28:
        output.delete(output.length() - 2, output.length());
        output.append(KeyWords.END_IF + " ");
        decreaseNesting();
        break;
      
      case 0x29:
        output.append(KeyWords.RETURN + " ");
        break;
      
      case 0x2A:
        output.append("0x2A ");
        break;
        
      case 0x2C:
        output.append(KeyWords.NULL + " ");
        break;
      
      case 0x2D:
        output.append(KeyWords.GLOBAL);
        breakLineAfterThis = false;
        requestNewLineAfterNext = true;
        break;
        
      case 0x2E:
        output.append(KeyWords.JUMP + " " + parseHex(4) + " ");
        break;
        
      case 0x2F: //TODO: followed by a long value defining how many long values follow
        output.append(KeyWords.RANDOM + KeyWords.SQUARED_BRACET_OPEN + parseRnd() + KeyWords.SQUARED_BRACET_CLOSE + "\n" + lineBegin());
        break;

      case 0x30:
        output.append("0x30 ");
        break;
      
      case 0x31:
        output.append("0x31 ");
        break;
      
      case 0x32:
        output.append(KeyWords.OR + " ");
        break;
      
      case 0x33:
        output.append(KeyWords.AND + " ");
        break;
      
      case 0x34:
        output.append("0x34 ");
        break;
        
      case 0x35:
        output.append("0x35 ");
        break;
        
      case 0x36:
        output.append("0x36 ");
        break;
        
      case 0x37:
        output.append("0x37 ");
        break;
        
      case 0x38:
        output.append("0x38 ");
        break;
      
      case 0x39:
        output.append(KeyWords.NOT + " ");
        break;
      
      case 0x3A:
        output.append("0x3A ");
        break;
        
      case 0x3B:
        output.append("0x3B ");
        break;
        
      case 0x40:
        output.append(KeyWords.RANDOM2 + KeyWords.SQUARED_BRACET_OPEN + parseRnd() + KeyWords.SQUARED_BRACET_CLOSE + "\n" + lineBegin());
        break;
        
      case 0x2B:
        return false;
    }
    
    if(breakLineAfterThis)
      output.append("\n" + lineBegin());
    breakLineAfterThis = false;
    if(requestNewLineAfterNext){
      requestNewLineAfterNext = false;
      breakLineAfterThis = true;
    }
    
    elseIfPossible = false;
    return true;
  }
  
  private String parseField(){
    byte[] figure = new byte[4];
    bs.read(figure);
    Integer key = ByteBuffer.wrap(figure).getInt();
    String name = fields.get(key);
    if(name == null){
      name = "pseudo" + (pseudo++);
      fields.put(key, name);
    }    
    return name;
  }
  
  private long parseLong(){
    byte[] b = new byte[4];
    bs.read(b);
    byte[] b2 = new byte[4];
    b2[0] = b[3];
    b2[1] = b[2];
    b2[2] = b[1];
    b2[3] = b[0];
    
    return ByteBuffer.wrap(b2).getInt();
  }
  
  private float parseFloat(){
    byte[] b = new byte[4];
    bs.read(b);
    
    byte[] b2 = new byte[4];
    b2[0] = b[3];
    b2[1] = b[2];
    b2[2] = b[1];
    b2[3] = b[0];
    
    float value = ByteBuffer.wrap(b2).getFloat();
    return value;
  }
  
  private String parseString(){
    long stringLength = parseLong();    
    byte[] stringBytes = new byte[(int)stringLength - 1];
    bs.read(stringBytes);
    bs.movePosition(1);
    return new String(stringBytes);
  }
  
  private String parseHex(int length){
    byte[] b = new byte[length];
    bs.read(b);
    String value = "0x";
    for(int i = 0; i < length; i++){
      String hex = Integer.toHexString((int)b[i] & 0xFF);
      while(hex.length() < 2)
        hex = "0" + hex;
      value += hex;
    }
    
    return value;
  }
  
  private String lineBegin(){
    return lineBegin(-1);
  }
  
  private void increaseNesting(){
    if(elseIfPossible)
      elseIfPossible = false;
    else
      indent += "  ";
  }
  
  private void decreaseNesting(){
    if(indent.length() > 1)
      indent = indent.substring(2);
  }
  
  private String lineBegin(long lineNumber){
    if(lineNumber < 0)
      return "        " + indent;
    
    return "#" + ((100000 + lineNumber) + "  " + indent).substring(1);
  }
  
  private String parseRnd(){
    String result = "";
    int count = (int)parseLong();
    result += count + KeyWords.SQUARED_BRACET_CLOSE + KeyWords.SQUARED_BRACET_OPEN;
    for(int i = 0; i < count; i++){
      result += parseLong();
      if(i+1 < count)
        result += KeyWords.DIVIDER + " ";
    }
    
    return result;
  }
}
